/*
 * This file is part of Catfish.
 * Copyright (C) 2010 Namazu Studios LLC
 * 
 * Catfish is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as 
 * published by the Free Software Foundation, either version 3 of 
 * the License, or (at your option) any later version.
 * 
 * Catfish is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with Catfish.  If not, please visit:
 *  
 * http://www.gnu.org/licenses/
 *  
 */
package com.namazustudios.catfish.reference;

import java.io.Serializable;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Transaction;
import com.google.inject.Inject;
import com.namazustudios.catfish.Catfish;

/**
 * Represents a "Weak" reference to another entity in the data store. If get is
 * called, it fetches the data from the data store. If the object is not found,
 * then it simply returns null.
 * 
 * Catfish will look for these objects and persist them with the given property
 * name on the enclosing Entity class.
 * 
 * @author patricktwohig
 * 
 */
public final class WeakReference<T> implements Serializable {
    
    private static final long serialVersionUID = 1254082318146962926L;

	private Key key;

	private Class<? extends T> type;

    transient private T t;

	@Inject
	transient private Catfish catfish;

	public WeakReference(Class<? extends T> type) {
		this.type = type;
	}

	/**
	 * Gets the value of the reference. Returns null if the value is not found
	 * in the datastore.
	 * 
	 * @return the Entity object referenced by the given
	 */
	public T get() {
		return get(null);
	}

	/**
	 * Gets the value of the reference. Returns null if hte value is not found
	 * in the datastore. This allows the caller to perform the get with the
	 * given Transaction.
	 * 
	 * @param transaction
	 * @return
	 */
	public T get(Transaction transaction) {
		if (t != null) {
			return t;
		} else if (key == null) {
			return null;
		} else {
			try {
				return t = catfish.get(type, transaction, key);
			} catch (EntityNotFoundException ex) {
				key = null;
				return t = null;
			}
		}
	}

	/**
	 * In case you need raw access to the key.
	 * 
	 * @return the key object.
	 */
	public Key getKey() {
		return key;
	}

	/**
	 * Gets the type of entity to set.
	 * 
	 * @return the type
	 */
	public Class<? extends T> getType() {
		return type;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
        return key == null ? 0 : key.hashCode();
    }

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
        return obj instanceof WeakReference<?> && 
            (
                type.equals(((WeakReference<?>)obj).type)
            ) &&
            (   (((WeakReference<?>)obj).key == null && key == null) ||
                ((WeakReference<?>)obj).key.equals(key)
            );
	}

	/**
	 * Sets the value of the reference and returns the object set.
	 * 
	 * @param t
	 * @return
	 */
	public T set(T t) {

		if (t == null) {
			key = null;
		} else {
			key = catfish.getKey(t);
		}

		return this.t = t;
	}

	/**
	 * Sets the key object.
	 * 
	 * @param key
	 *            the key object.
	 */
	public void setKey(Key key) {

		if (key != null && !key.getKind().equals(catfish.getKind(type))) {
			throw new IllegalArgumentException("Key kind must match type.");
		}

		t = null;
		this.key = key;
	}

}
